Features
Everything your product says by email.
Four parts, one API, one bill. Codes and receipts, newsletters and launches, sequences that run themselves, and the plumbing that keeps all of it out of the spam folder.
#01 – Transactional mail
Codes, receipts, and alerts your users can count on.
-
One POST, or point your SMTP client at us. Templates, attachments, tags, and an idempotency key so a retried request never sends twice.
-
Typed clients for Node, Python, PHP, Go, Ruby, and Rust — with the same names as the API, so the docs and the autocomplete agree.
-
Point an address at Herald and receive every reply as structured JSON: parsed body, attachments, headers, and a verified sender.
-
A signed event for every delivery, open, click, bounce, and complaint, retried with backoff until you return a 200.
curl https://api.herald.dev/v1/send \
-H "Authorization: Bearer $HERALD_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "Mara <mara@herald.dev>",
"to": "sam@acme.com",
"subject": "Welcome to Herald",
"template": "welcome",
"data": { "name": "Sam" }
}'
import { Herald } from 'herald';
const herald = new Herald(process.env.HERALD_KEY);
await herald.send({
from: 'Mara <mara@herald.dev>',
to: 'sam@acme.com',
subject: 'Welcome to Herald',
template: 'welcome',
data: { name: 'Sam' },
});
from herald import Herald
herald = Herald(os.environ["HERALD_KEY"])
herald.send(
from_="Mara <mara@herald.dev>",
to="sam@acme.com",
subject="Welcome to Herald",
template="welcome",
data={"name": "Sam"},
)
$herald = new Herald\Client(getenv('HERALD_KEY'));
$herald->send([
'from' => 'Mara <mara@herald.dev>',
'to' => 'sam@acme.com',
'subject' => 'Welcome to Herald',
'template' => 'welcome',
'data' => ['name' => 'Sam'],
]);
client := herald.New(os.Getenv("HERALD_KEY"))
_, err := client.Send(ctx, herald.Message{
From: "Mara <mara@herald.dev>",
To: "sam@acme.com",
Subject: "Welcome to Herald",
Template: "welcome",
Data: map[string]any{"name": "Sam"},
})
herald = Herald::Client.new(ENV["HERALD_KEY"])
herald.send(
from: "Mara <mara@herald.dev>",
to: "sam@acme.com",
subject: "Welcome to Herald",
template: "welcome",
data: { name: "Sam" }
)
curl https://api.herald.dev/v1/send \
-H "Authorization: Bearer $HERALD_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "Mara <mara@herald.dev>",
"to": "sam@acme.com",
"subject": "Welcome to Herald",
"template": "welcome",
"data": { "name": "Sam" }
}'
import { Herald } from 'herald';
const herald = new Herald(process.env.HERALD_KEY);
await herald.send({
from: 'Mara <mara@herald.dev>',
to: 'sam@acme.com',
subject: 'Welcome to Herald',
template: 'welcome',
data: { name: 'Sam' },
});
from herald import Herald
herald = Herald(os.environ["HERALD_KEY"])
herald.send(
from_="Mara <mara@herald.dev>",
to="sam@acme.com",
subject="Welcome to Herald",
template="welcome",
data={"name": "Sam"},
)
$herald = new Herald\Client(getenv('HERALD_KEY'));
$herald->send([
'from' => 'Mara <mara@herald.dev>',
'to' => 'sam@acme.com',
'subject' => 'Welcome to Herald',
'template' => 'welcome',
'data' => ['name' => 'Sam'],
]);
client := herald.New(os.Getenv("HERALD_KEY"))
_, err := client.Send(ctx, herald.Message{
From: "Mara <mara@herald.dev>",
To: "sam@acme.com",
Subject: "Welcome to Herald",
Template: "welcome",
Data: map[string]any{"name": "Sam"},
})
herald = Herald::Client.new(ENV["HERALD_KEY"])
herald.send(
from: "Mara <mara@herald.dev>",
to: "sam@acme.com",
subject: "Welcome to Herald",
template: "welcome",
data: { name: "Sam" }
)
- from
- sam@acme.com
- subject
- Re: Your invoice for March
- reply
- Paid this morning, thanks.
- attachments
- []
- in_reply_to
- msg_8f2k
- auth
- SPF · DKIM · DMARC
- sam@acme.com Delivered 1.1s
- j.lee@northwind.io Delivered 0.9s
- riley@marsh.studio Delivered 1.4s
- ops@frostline.io Deferred 12.0s
- old-address@example.org Bounced 0.3s
- hello@looply.app Delivered 1.2s
- team@orbital.dev Delivered 1.0s
#02 – Broadcasts
Newsletters and launches that land in the inbox, not the tab.
-
Write a broadcast, pick a segment, schedule it. Newsletters, launches, and the occasional apology all go out the same way.
-
Herald ramps a new domain over three weeks so mailbox providers learn to trust it before you send your first big list.
-
Filter contacts by property, event, or engagement, and the segment updates itself as people qualify or fall out.
-
Write in markdown, preview in every major client, and ship a message that renders the same in Gmail and Outlook.
Campaigns
Broadcasts to a segment of your contacts.
-
Sending 24 Mar, 2026 · 10:24
Inbound is live
Subject: Replies now arrive as JSON
-
Sent 20 Mar, 2026 · 09:40
March notes
Subject: What we shipped, and one thing we broke
-
Sent 12 Mar, 2026 · 08:15
Warmup week three
Subject: Your domain is ready for the big list
-
Draft Not scheduled
Pricing update
Subject: A smaller bill for most of you
- 1
- 3
- 5
- 7
- 9
- 11
- 13
- 14
Day 14 of 21 · cap doubles while bounces stay low
- Where plan is pro
- And last_seen within 30 days
- And emails_opened at least 3
2,410 contacts match right now
# What we shipped
Inbound mail is live. Point an address at Herald and every reply arrives as JSON.
[Read the changelog](/changelog)
What we shipped
Inbound mail is live. Point an address at Herald and every reply arrives as JSON.
Read the changelog#03 – Automations
Sequences that send themselves, from signup to renewal.
-
Welcome series, onboarding nudges, and win-back campaigns that run themselves once a contact enters them.
-
Start a sequence from a signup, a property change, or an event your product sends, and stop it the moment they convert.
-
Opens, clicks, and conversions per step, so you can see which message does the work and which one gets skipped.
-
Trigger
Contact is added to New signups
-
Welcome to Herald From Mara <mara@herald.dev>
-
Wait for 2 days
-
Your first send, step by step Skipped if they have already sent
-
Wait for 5 days
-
Sequence ends
When every email has gone out, or the moment the contact upgrades.
- contact.created Starts Welcome series
- plan.changed → pro Starts Getting the most from Pro
- invoice.overdue Starts Payment reminders
- contact.inactive_30d Starts Win-back
-
Delivered 98.6% +0.4
-
Opened 61.2% +3.1
-
Clicked 18.7% +1.9
-
Upgraded 4.3% +0.8
#04 – Deliverability
The part nobody sees until it breaks.
-
SPF, DKIM, and DMARC set up in one screen, with a check that tells you exactly which record is missing.
-
Above 500,000 messages a month you get your own IP and a reputation nobody else can dent.
-
Bounces, complaints, and unsubscribes land on a shared suppression list so no workspace can email someone who opted out.
-
Seed tests against the big providers before every campaign, and a live log of every delivery, deferral, and bounce.
- TXT mail.acme.com Verified
- CNAME herald._domainkey.mail.acme.com Verified
- TXT _dmarc.mail.acme.com Verified
- MX inbound.mail.acme.com Pending
- 1
- 3
- 5
- 7
- 9
- 11
- 13
- 14
Day 14 of 21 · cap doubles while bounces stay low
- sam@acme.com Delivered 1.1s
- j.lee@northwind.io Delivered 0.9s
- riley@marsh.studio Delivered 1.4s
- ops@frostline.io Deferred 12.0s
- old-address@example.org Bounced 0.3s
- hello@looply.app Delivered 1.2s
- team@orbital.dev Delivered 1.0s
-
Delivered 98.6% +0.4
-
Opened 61.2% +3.1
-
Clicked 18.7% +1.9
-
Upgraded 4.3% +0.8
Workspaces
Every product, one bill.
Create a workspace for each product, client, or environment you run, each with its own domains, contacts, templates, and API keys — and one invoice at the end of the month.
- Acme mail.acme.com
- Frostline frostline.io
- Staging staging.acme.com
- New workspace
Questions, answered.
Volume, overage, warmup, and what happens when a message bounces.
What does "priced by volume" mean?
You pay for the emails you send, not the contacts you store. A list of 200,000 people you email once a quarter costs almost nothing; a list of 500 people you email hourly costs more. That is the fair way round.
What happens if I go over my plan?
Nothing stops. We keep sending and bill the extra at the plan's overage rate, which is shown on the plan before you pick it. You can set a hard cap in settings if you would rather we paused.
Do I need to warm up a new domain?
Yes, and Herald does it for you. A new domain ramps over about three weeks, starting with your most engaged contacts, so mailbox providers see a sender that behaves before they see one that sends in bulk.
What happens when a message bounces?
A hard bounce adds the address to your suppression list and fires a bounce webhook. A soft bounce is retried for up to 72 hours. Either way the delivery log shows you what happened and why.
Can my agent really send email on its own?
Yes. The MCP server exposes send, draft, and report tools, and the skill file teaches the agent the rules: idempotency keys, rate limits, template syntax, and when it should ask a human first. You choose which tools each key can call.
Can I bring my own IP?
On Scale and Enterprise you get a dedicated IP that only your workspaces send from. Below that you share a pool we manage and monitor, which for most senders is the better deal.
Send your first message today.
Three thousand emails a month are free, no card required. Verify a domain, grab a key, and your first send is one request away.